package pw.jeb;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @Author Eric
 * @Desc
 * @Date Create in 18-2-7 下午2:58
 * @Modified By
 */
public class Jeb {
    public static final String DEFAULT_IDENTIFIER = "default";

    /** All registered event handlers, indexed by event type. */
    private final ConcurrentMap<String, Set<EventHandler>> handlersByType =
            new ConcurrentHashMap<String, Set<EventHandler>>();


    /**
     * 实例标识
     * */
    private final String identifier;

    /**
     * 运行线程检查者
     * */
    private final ThreadEnforcer enforcer;

    /**
     * 类的反射处理，主要是针对缓存的操作
     * */
    private final HandlerFinder handlerFinder;

    /**
     * 多线程的处理
     * */
    private final ThreadLocal<ConcurrentLinkedQueue<Jeb.EventWithHandler>> eventsToDispatch =
            new ThreadLocal<ConcurrentLinkedQueue<Jeb.EventWithHandler>>() {
                @Override protected ConcurrentLinkedQueue<Jeb.EventWithHandler> initialValue() {
                    return new ConcurrentLinkedQueue<Jeb.EventWithHandler>();
                }
            };

    /** 多线程状态处理 */
    private final ThreadLocal<Boolean> isDispatching = new ThreadLocal<Boolean>() {
        @Override protected Boolean initialValue() {
            return false;
        }
    };

    public Jeb() {
        this(DEFAULT_IDENTIFIER);
    }

    public Jeb(String identifier) {
        this(ThreadEnforcer.ANY, identifier);
    }

    public Jeb(ThreadEnforcer enforcer) {
        this(enforcer, DEFAULT_IDENTIFIER);
    }

    public Jeb(ThreadEnforcer enforcer, String identifier) {
        this(enforcer, identifier, HandlerFinder.ANNOTATED);
    }

    Jeb(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
        this.enforcer =  enforcer;
        this.identifier = identifier;
        this.handlerFinder = handlerFinder;
    }

    @Override public String toString() {
        return "[Jeb \"" + identifier + "\"]";
    }

    /**
     * 注册订阅者对象
     * @param object 不允许为Null
     */
    public Jeb register(Object object) {
        if (object == null) {
            throw new NullPointerException("Object to register must not be null.");
        }

        Map<String, Set<EventHandler>> foundHandlersMap = handlerFinder.findAllSubscribers(object);
        for (String type: foundHandlersMap.keySet()) {
            Set<EventHandler> handlers = handlersByType.get(type);
            if (handlers == null) {
                //concurrent put if absent
                Set<EventHandler> handlersCreation = new CopyOnWriteArraySet<EventHandler>();
                handlers = handlersByType.putIfAbsent(type, handlersCreation);
                if (handlers == null) {
                    handlers = handlersCreation;
                }
            }
            final Set<EventHandler> foundHandlers = foundHandlersMap.get(type);
            if (!handlers.addAll(foundHandlers)) {
                throw new IllegalArgumentException("Object already registered.");
            }
        }

        return this;
    }

    /**
     * 取消注册订阅者对象
     * @param object 不允许为Null
     */
    public Jeb unregister(Object object) {
        if (object == null) {
            throw new NullPointerException("Object to unregister must not be null.");
        }

        Map<String, Set<EventHandler>> handlersInListener = handlerFinder.findAllSubscribers(object);
        for (Map.Entry<String, Set<EventHandler>> entry : handlersInListener.entrySet()) {
            Set<EventHandler> currentHandlers = handlersByType.get(entry.getKey());
            Collection<EventHandler> eventMethodsInListener = entry.getValue();

            if (currentHandlers == null || !currentHandlers.containsAll(eventMethodsInListener)) {
                throw new IllegalArgumentException(
                        "Missing event handler for an annotated method. Is " + object.getClass()
                                + " registered?");
            }

            for (EventHandler handler : currentHandlers) {
                if (eventMethodsInListener.contains(handler)) {
                    handler.invalidate();
                }
            }
            currentHandlers.removeAll(eventMethodsInListener);
        }
        return this;

    }

    /**
     * 广播事件,并在当前线程分发
     * @param event
     */
    public Jeb post(Event event) {
        if (event == null) {
            throw new NullPointerException("Event to post must not be null.");
        }

        String type = event.type();

        Set<EventHandler> wrappers = handlersByType.get(type);

        if (wrappers != null && !wrappers.isEmpty()) {
            for (EventHandler wrapper : wrappers) {
                enqueueEvent(event, wrapper);
            }
        }

        dispatchQueuedEvents();
        return this;
    }

    /**
     * 广播事件,并在主线程分发
     * @param event
     */
    public Jeb postInMain(final Event event) {
        if (event == null) {
            throw new NullPointerException("Event to post must not be null.");
        }


        enforcer.run(new Runnable() {
            public void run() {
                String type = event.type();

                Set<EventHandler> wrappers = handlersByType.get(type);

                if (wrappers != null && !wrappers.isEmpty()) {
                    for (EventHandler wrapper : wrappers) {
                        enqueueEvent(event, wrapper);
                    }
                }

                dispatchQueuedEvents();
            }
        });

        return this;

    }

    /**
     * Queue the {@code event} for dispatch during {@link #dispatchQueuedEvents()}. Events are queued in-order of
     * occurrence so they can be dispatched in the same order.
     */
    protected void enqueueEvent(Event event, EventHandler handler) {
        eventsToDispatch.get().offer(new Jeb.EventWithHandler(event, handler));
    }

    /**
     * Drain the queue of events to be dispatched. As the queue is being drained, new events may be posted to the end of
     * the queue.
     */
    protected void dispatchQueuedEvents() {
        // don't dispatch if we're already dispatching, that would allow reentrancy and out-of-order events. Instead, leave
        // the events to be dispatched after the in-progress dispatch is complete.
        if (isDispatching.get()) {
            return;
        }

        isDispatching.set(true);
        try {
            while (true) {
                Jeb.EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
                if (eventWithHandler == null) {
                    break;
                }

                if (eventWithHandler.handler.isValid()) {
                    dispatch(eventWithHandler.event, eventWithHandler.handler);
                }
            }
        } finally {
            isDispatching.set(false);
        }
    }

    /**
     * Dispatches {@code event} to the handler in {@code wrapper}.  This method is an appropriate override point for
     * subclasses that wish to make event delivery asynchronous.
     *
     * @param event event to dispatch.
     * @param wrapper wrapper that will call the handler.
     */
    protected void dispatch(Event event, EventHandler wrapper) {
        try {
            wrapper.handleEvent(event);
        } catch (InvocationTargetException e) {
            throwRuntimeException(
                    "Could not dispatch event: " + event.getClass() + " to handler " + wrapper, e);
        }
    }

    /**
     * Throw a {@link RuntimeException} with given message and cause lifted from an {@link
     * InvocationTargetException}. If the specified {@link InvocationTargetException} does not have a
     * cause, neither will the {@link RuntimeException}.
     */
    private static void throwRuntimeException(String msg, InvocationTargetException e) {
        Throwable cause = e.getCause();
        if (cause != null) {
            throw new RuntimeException(msg + ": " + cause.getMessage(), cause);
        } else {
            throw new RuntimeException(msg + ": " + e.getMessage(), e);
        }
    }

    /** Simple struct representing an event and its handler. */
    static class EventWithHandler {
        final Event event;
        final EventHandler handler;

        public EventWithHandler(Event event, EventHandler handler) {
            this.event = event;
            this.handler = handler;
        }
    }
}
